﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
using System.IO;

namespace SharpPTCLib
{
    /// <summary>
    /// Framebuffer library with keyboard support
    /// By E.Sandberg, 09/09/17
    /// </summary>
    public class SharpPTC
    {
        private IVideoDriver _videoDriver;
        private System.Windows.Forms.Form _form;
        [DllImport("user32.dll")]
        static extern bool GetKeyboardState(byte[] lpKeyState);

        //SharpPTC
        public int Width { get { return _width; } }
        public int Height { get { return _height; } }
        public int[] pixel;
        private int _width;
        private int _height;
        private int _clsColor = 0x000000;
        private int _drawColor = 0xFF0000;

        //Font
        private int[,] _fontData;
        private int _fontCount;
        private int _fontWidth;
        private int _fontHeight;

        //Keyboard
        private byte[] _oldKeyState = new byte[256];
        private byte[] _keyState = new byte[256];

        #region Video
        /// <summary>
        /// Starts the video driver. 
        /// </summary>
        /// <param name="videoDriver">Video driver to use</param>
        /// <param name="title">Window title</param>
        /// <param name="width">Width of buffer</param>
        /// <param name="height">Height of buffer</param>
        /// <param name="zoom">Software scaling</param>
        /// <param name="fullscreen">Not supported</param>
        /// <returns></returns>
        private bool Open(IVideoDriver videoDriver, string title, int width, int height, int zoom, bool fullscreen)
        {
            if (!_videoDriver.Open(title, width, height, zoom, fullscreen))
                return false;

            pixel = new int[width * height];
            _width = width;
            _height = height;
            return true;
        }

        /// <summary>
        /// Flips the screen. Also yields processor time to Windows
        /// </summary>
        /// <returns></returns>
        public bool Update()
        {
            return _videoDriver.Update(pixel);
        }

        /// <summary>
        /// Closes the video driver
        /// </summary>
        public void Close()
        {
            _videoDriver.Close();
        }
        #endregion

        #region Font
        private void loadFont()
        {
            BinaryReader br = new BinaryReader(new MemoryStream(Properties.Resources.font));
            _fontCount = br.ReadInt32();
            _fontWidth = br.ReadInt32();
            _fontHeight = br.ReadInt32();
            _fontData = new int[_fontCount, _width * _height];

            for (int i = 0; i < _fontCount; i++)
                for (int y = 0; y < _fontHeight; y++)
                    for (int x = 0; x < _fontWidth; x++)
                    {
                        bool color = br.ReadBoolean();
                        if (color)
                            _fontData[i, (y * _fontWidth) + x] = 1;
                    }
            br.Close();
        }

        public void DrawLetter(int x, int y, char c)
        {
            int num = c - 32;
            for (int yp = 0; yp < _fontHeight; yp++)
                for (int xp = 0; xp < _fontWidth; xp++)
                {
                    if (_fontData[num, (yp * _fontWidth) + xp] == 1)
                        DrawPixel(x + xp, y + yp);
                }
        }

        public void DrawString(int x, int y, string text)
        {
            foreach (char c in text)
                DrawLetter(x += _fontWidth, y, c);
        }
        #endregion

        #region Helper functions
        /// <summary>
        /// Converts a System.Drawing.Color to an integer
        /// </summary>
        /// <param name="color"></param>
        /// <returns></returns>
        public int ColorToInt32(Color color)
        {
            return (color.R << 16) + (color.G << 8) + color.B;
        }

        public int RGBToInt32(int r, int g, int b)
        {
            return (r << 16) + (g << 8) + b;
        }

        public bool IsKeyDown(Scancode scancode)
        {
            if((_keyState[(int)scancode] & 0x80) == 0x80)
                return true;
            return false;
        }

        public bool IsKeyPressed(Scancode scancode)
        {
            if (((_keyState[(int)scancode] & 0x80) == 0x80) && (_oldKeyState[(int)scancode] & 0x80) != 0x80)
                return true;
            return false;
        }

        /// <summary>
        /// Sets the color used to draw primitives
        /// </summary>
        /// <param name="color"></param>
        public void SetDrawColor(Color color)
        {
            _drawColor = ColorToInt32(color);
        }

        /// <summary>
        /// Sets the color used to draw primitives
        /// </summary>
        /// <param name="color"></param>
        public void SetDrawColor(int color)
        {
            _drawColor = color;
        }

        public void SetDrawColor(int r, int g, int b)
        {
            _drawColor = (r << 16) + (g << 8) + b;
        }

        /// <summary>
        /// Sets the color used to clear the screen
        /// </summary>
        /// <param name="color"></param>
        public void SetClsColor(Color color)
        {
            _clsColor = ColorToInt32(color);
        }

        /// <summary>
        /// Sets the color used to clear the screen
        /// </summary>
        /// <param name="color"></param>
        public void SetClsColor(int color)
        {
            _clsColor = color;
        }

        /// <summary>
        /// Starts both video and DirectInput
        /// </summary>
        /// <param name="title">Window title</param>
        /// <param name="width">Width of buffer</param>
        /// <param name="height">Height of buffer</param>
        /// <param name="zoom">Software scaling</param>
        /// <param name="fullscreen">Not supported</param>
        /// <returns></returns>
        public bool StartAll(string title, int width, int height, int zoom, bool fullscreen)
        {
            _videoDriver = new DDrawDriver();
            if (Open(_videoDriver, title, width, height, zoom, fullscreen))
            {
                loadFont();
                GetKeyboardState(_keyState);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Draws a pixel at the specified location
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void DrawPixel(int x, int y)
        {
            if(x > 0 && x < _width && y > 0 && y < _height)
                pixel[(y * _width) + x] = _drawColor;
        }

        /// <summary>
        /// Draws a pixel without checking buffer bounds. Dangerous.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void DrawPixelFast(int x, int y)
        {
            pixel[(y * _width) + x] = _drawColor;
        }

        /// <summary>
        /// Draws a filled rectangle
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void DrawRectangle(int x, int y, int width, int height)
        {
            for (int x1 = x; x1 < x + width; x1++)
                for (int y1 = y; y1 < y + height; y1++)
                    DrawPixel(x1, y1);
        }

        /// <summary>
        /// Fills the screen with cls color
        /// </summary>
        public void Cls()
        {
            for (int x = 0; x < _width; x++)
                for (int y = 0; y < _height; y++)
                    pixel[(y * _width) + x] = _clsColor;
        }

        /// <summary>
        /// Draws a line using Bresenhams algorithm
        /// </summary>
        /// <param name="x0"></param>
        /// <param name="y0"></param>
        /// <param name="x1"></param>
        /// <param name="y1"></param>
        public void DrawLine(int x0, int y0, int x1, int y1)
        {
            bool steep = Math.Abs(y1 - y0) > Math.Abs(x1 - x0);
            if (steep)
            {
                int xtemp = x0;
                x0 = y0;
                y0 = xtemp;

                xtemp = x1;
                x1 = y1;
                y1 = xtemp;
            }
            if (x0 > x1)
            {
                int xtemp = x0;
                x0 = x1;
                x1 = xtemp;

                xtemp = y0;
                y0 = y1;
                y1 = xtemp;
            }
            int deltax = x1 - x0;
            int deltay = Math.Abs(y1 - y0);
            int error = deltax / 2;
            int ystep;
            int y = y0;

            if (y0 < y1)
                ystep = 1;
            else
                ystep = -1;

            for (int x = x0; x < x1; x++)
            {
                if (steep)
                    DrawPixel(y, x);
                else
                    DrawPixel(x, y);

                error = error - deltay;
                if (error < 0)
                {
                    y = y + ystep;
                    error = error + deltax;
                }
            }
        }

        /// <summary>
        /// Blits an 'image' to the screen.
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <param name="width">Width of image</param>
        /// <param name="img">Image buffer</param>
        /// <param name="maskValue">Value mask</param>
        public void Blit(int x, int y, int width, int[] img, int maskValue)
        {
            int yh = 0;
            int xh = 0;
            int oldColor = _drawColor;
            for(int i = 0; i < img.Length; i++)
            {
                if (img[i] != maskValue)
                {
                    _drawColor = img[i];
                    DrawPixel(x + xh, y + yh);
                }
                xh++;
                if(xh >= width)
                {
                    xh = 0;
                    yh++;
                }
            }
            _drawColor = oldColor;
        }

        /// <summary>
        /// Updates video and polls the keyboard buffer
        /// </summary>
        public bool UpdateAll()
        {
            _keyState.CopyTo(_oldKeyState, 0);
            GetKeyboardState(_keyState);
            return Update();
        }

        /// <summary>
        /// Shuts down all subsystems
        /// </summary>
        public void CloseAll()
        {
            Close();
        }
        #endregion

        
    }
}
